/*
 *     Copyright 2025 The Dragonfly Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package util

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
)

const (
	// defaultDufsFileServerEndpoint is the default endpoint of the dufs file server.
	defaultDufsFileServerEndpoint = "http://dufs.dragonfly-e2e.svc"

	// defaultDufsFileServerLocalDir is the default local directory of the dufs file server.
	defaultDufsFileServerLocalDir = "/tmp/artifact/dufs"
)

// dufsFileServer represents a file server implemented by project dufs(https://github.com/sigoden/dufs).
type dufsFileServer struct {
	// endpoint is the endpoint of the file server.
	endpoint *url.URL

	// localDir is the local directory of the file server.
	localDir string
}

// newDufsFileServer creates a new file server.
func newDufsFileServer(opts ...FileServerOption) (*dufsFileServer, error) {
	fileServerOptions := &fileServer{
		endpoint: defaultDufsFileServerEndpoint,
		localDir: defaultDufsFileServerLocalDir,
	}

	for _, opt := range opts {
		opt(fileServerOptions)
	}

	// parse the endpoint
	var u *url.URL
	u, err := url.Parse(fileServerOptions.endpoint)
	if err != nil {
		return nil, err
	}

	// ensure the localDir exists
	if err = os.MkdirAll(fileServerOptions.localDir, 0777); err != nil {
		return nil, err
	}

	fileServer := &dufsFileServer{
		endpoint: u,
		localDir: fileServerOptions.localDir,
	}

	return fileServer, nil
}

// GenerateFile generates a file by the size and returns File instance.
func (fs *dufsFileServer) GenerateFile(size FileSize) (*File, error) {
	// create file in the localDir
	data := make([]byte, size)
	_, err := rand.Read(data)
	if err != nil {
		return nil, err
	}

	hash := sha256.Sum256(data)
	fileName := hex.EncodeToString(hash[:])
	filePath := filepath.Join(fs.localDir, fileName)
	if err = os.WriteFile(filePath, data, 0666); err != nil {
		return nil, err
	}

	// get file info
	info, err := os.Stat(filePath)
	if err != nil {
		return nil, err
	}

	file := &File{
		info:        info,
		localPath:   filePath,
		downloadURL: fmt.Sprintf("%s/%s", fs.endpoint.String(), fileName),
	}

	return file, nil
}

// DeleteFile deletes the file by the file info.
func (fs *dufsFileServer) DeleteFile(info os.FileInfo) error {
	// delete the file in the local dir
	filepath := filepath.Join(fs.localDir, info.Name())
	return os.RemoveAll(filepath)
}

// Purge cleans up all files generated by the file server.
func (fs *dufsFileServer) Purge() error {
	return os.RemoveAll(fs.localDir)
}
